/*
 * Copyright 2002-2004 The Apache Software Foundation.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *   http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 * 
 */
package org.apache.jmeter.protocol.java.sampler;

import java.util.HashSet;
import java.util.Iterator;
import java.util.Map;
import java.util.Set;

import org.apache.commons.beanutils.BeanUtils;
import org.apache.commons.chain.Command;
import org.apache.commons.chain.Context;
import org.apache.jmeter.config.Arguments;
import org.apache.jmeter.engine.event.LoopIterationEvent;
import org.apache.jmeter.samplers.AbstractSampler;
import org.apache.jmeter.samplers.Entry;
import org.apache.jmeter.samplers.SampleResult;
import org.apache.jmeter.testelement.TestListener;
import org.apache.jmeter.testelement.property.TestElementProperty;
import org.apache.jorphan.logging.LoggingManager;
import org.apache.log.Logger;

/**
 * A sampler for executing custom Commons Chain Commands
 * 
 * @author Edgar Poce
 */
public class ChainSampler extends AbstractSampler implements TestListener
{
    /**
     * Comment for <code>serialVersionUID</code>
     */
    private static final long serialVersionUID = 3618986663356151348L;

    /**
     * chainCtx key
     */
    public static final String CHAINS_CONTEXT = "chainCtx";

    /**
     * The Command instance used by this sampler to actually perform the sample.
     */
    private transient Command command = null;

    /**
     * Logging
     */
    private static transient Logger log = LoggingManager.getLoggerForClass();

    /**
     * Set used to register all active JavaSamplers. This is used so that the
     * samplers can be notified when the test ends.
     */
    private static Set allSamplers = new HashSet();

    /**
     * Create a JavaSampler.
     */
    public ChainSampler()
    {
        setArguments(new Arguments());
        synchronized (allSamplers)
        {
            allSamplers.add(this);
        }
    }

    /**
     * Set the arguments (parameters) for the JavaSamplerClient to be executed
     * with.
     * 
     * @param args
     *            the new arguments. These replace any existing arguments.
     */
    public void setArguments(Arguments args)
    {
        setProperty(new TestElementProperty(JavaSampler.ARGUMENTS, args));
    }

    /**
     * Get the arguments (parameters) for the JavaSamplerClient to be executed
     * with.
     * 
     * @return the arguments
     */
    public Arguments getArguments()
    {
        return (Arguments) getProperty(JavaSampler.ARGUMENTS).getObjectValue();
    }

    /**
     * Releases Command Client.
     */
    private void releaseClient()
    {
        this.command = null;
    }

    /**
     * Sets the Classname attribute of the JavaConfig object
     * 
     * @param classname
     *            the new Classname value
     */
    public void setClassname(String classname)
    {
        setProperty(JavaSampler.CLASSNAME, classname);
    }

    /**
     * Gets the Classname attribute of the JavaConfig object
     * 
     * @return the Classname value
     */
    public String getClassname()
    {
        return getPropertyAsString(JavaSampler.CLASSNAME);
    }

    /**
     * Performs a test sample.
     * 
     * The <code>sample()</code> method retrieves the reference to the command
     * and calls its <code>execute()</code> method.
     * 
     * @param entry
     *            the Entry for this sample
     * @return test SampleResult
     */
    public SampleResult sample(Entry entry)
    {
        SampleResult results = new SampleResult();
        try
        {
            if (command == null)
            {
                log.debug(whoAmI() + "Creating Command");
                createCommand();
            }

            updateCommand();
            results.setSampleLabel(this.getName());
            Context ctx = new JMeterContextAdapter(getThreadContext());
            results.sampleStart();
            command.execute(ctx);
            results.sampleEnd();
            results.setSuccessful(true);
        } catch (Exception e)
        {
            results.setSuccessful(false);
            log.error("Unable to run test", e);
        }
        return results;
    }

    /**
     * Returns reference to <code>Command</code>.
     * 
     * @return Command reference.
     */
    private Command createCommand() throws Exception
    {
        Class javaClass = Class.forName(getClassname().trim(), false, Thread
            .currentThread().getContextClassLoader());

        command = (Command) javaClass.newInstance();

        if (log.isDebugEnabled())
        {
            log.debug(whoAmI() + "\tCreated:\t" + getClassname() + "@"
                    + Integer.toHexString(command.hashCode()));
        }

        return command;
    }

    /**
     * Updates the command attributes
     * 
     * @throws Exception
     */
    private void updateCommand() throws Exception
    {
        Map descrip = BeanUtils.describe(command);
        Iterator iter = descrip.keySet().iterator();
        while (iter.hasNext())
        {
            String key = (String) iter.next();
            Object value = this.getArguments().getArgumentsAsMap().get(key);
            if (value != null && value.toString().length() > 0)
            {
                BeanUtils.setProperty(command, key, value);
            }
        }
    }

    /**
     * Retrieves reference to JavaSamplerClient.
     * 
     * Convience method used to check for null reference without actually
     * creating a JavaSamplerClient
     * 
     * @return reference to JavaSamplerClient NOTUSED private JavaSamplerClient
     *         retrieveJavaClient() { return javaClient; }
     */

    /**
     * Generate a String identifier of this instance for debugging purposes.
     * 
     * @return a String identifier for this sampler instance
     */
    private String whoAmI()
    {
        StringBuffer sb = new StringBuffer();
        sb.append(Thread.currentThread().getName());
        sb.append("@");
        sb.append(Integer.toHexString(hashCode()));
        return sb.toString();
    }

    // TestListener implementation
    /* Implements TestListener.testStarted() */
    public void testStarted()
    {
        log.debug(whoAmI() + "\ttestStarted");
    }

    /* Implements TestListener.testStarted(String) */
    public void testStarted(String host)
    {
        log.debug(whoAmI() + "\ttestStarted(" + host + ")");
    }

    /**
     * Method called at the end of the test. This is called only on one instance
     * of ChainSampler. This method will loop through all of the other samplers
     * which have been registered (automatically in the constructor) and notify
     * them that the test has ended, allowing the ChainSamplerClients to
     * cleanup.
     */
    public void testEnded()
    {
        log.debug(whoAmI() + "\ttestEnded");
        synchronized (allSamplers)
        {
            Iterator i = allSamplers.iterator();
            while (i.hasNext())
            {
                ChainSampler sampler = (ChainSampler) i.next();
                sampler.releaseClient();
                i.remove();
            }
        }
    }

    /* Implements TestListener.testEnded(String) */
    public void testEnded(String host)
    {
        testEnded();
    }

    /* Implements TestListener.testIterationStart(LoopIterationEvent) */
    public void testIterationStart(LoopIterationEvent event)
    {
    }
}
